package com.panopset.sf;

import java.awt.Color;
import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JComponent;
import javax.swing.border.TitledBorder;

import com.panopset.Logger;
import com.panopset.compat.Strings;
import com.panopset.io.FileProps;

/**
 * Provide set of small, medium, and large fonts.
 *
 * @author Karl Dinwiddie
 */
public final class FontManager {

    /**
     * &quot;com.panopset.sf.FontManager.size&quot;.
     */
    public static final String FONT_SIZE_KEY

    = "com.panopset.sf.FontManager.size";

    /**
     * Lucida Sans Regular.
     */
    public static final String LUCIDIA = "Lucida Sans Regular";

    /**
     * Lucida Sans Bold.
     */
    public static final String LUCIDIA_BOLD = "Lucida Sans Bold";

    /**
     * Lucida Sans Oblique.
     */
    public static final String LUCIDIA_OBLIQUE = "Lucida Sans Oblique";

    /**
     * Lucida Sans Bold Oblique.
     */
    public static final String LUCIDIA_BOLD_OB = "Lucida Sans Bold Oblique";

    /**
     * Lucida Bright Regular.
     */
    public static final String L_BRIGHT = "Lucida Bright Regular";

    /**
     * Lucida Bright Bold.
     */
    public static final String L_BRIGHT_BOLD = "Lucida Bright Bold";

    /**
     * Lucida Bright Italic.
     */
    public static final String L_BRIGHT_OBLIQUE = "Lucida Bright Italic";

    /**
     * Lucida Bright Bold Italic.
     */
    public static final String L_BRIGHT_BOLD_OB = "Lucida Bright Bold Italic";

    /**
     * Lucida Sans Typewriter Regular.
     */
    public static final String MONOSPACE = "Lucida Sans Typewriter Regular";

    /**
     * Lucida Sans Typewriter Bold.
     */
    public static final String MONOSPACE_BOLD = "Lucida Sans Typewriter Bold";

    /**
     * Lucida Sans Typewriter Oblique.
     */
    public static final String MONOSPACE_OBLIQUE

    = "Lucida Sans Typewriter Oblique";

    /**
     * Lucida Sans Typewriter Bold Oblique.
     */
    public static final String MONOSPACE_BOLD_OB

    = "Lucida Sans Typewriter Bold Oblique";

    /**
     * Arial.
     */
    public static final String ARIAL = "Arial";
    /**
     * Dialog.
     */
    public static final String DIALOG = "Dialog";
    /**
     * DocumentManager key.
     */
    public static final String KEY = "com.panopset.fontsize";

    /**
     * Monospace Font.
     */
    private Font monospace;

    /**
     * Bold arial Font.
     */
    private Font boldArial;

    /**
     * Plain arial Font.
     */
    private Font plainArial;

    /**
     * Border title font.
     */
    private Font borderTitle;

    /**
     * @param size
     *            Size.
     */
    public void setSize(final Size size) {
        if (size == null) {
            return;
        }
        monospace = new Font(MONOSPACE, Font.PLAIN, size.getValue());
        boldArial = new Font(ARIAL, Font.BOLD, size.getValue());
        plainArial = new Font(ARIAL, Font.PLAIN, size.getValue());
        int dip = 2;
        if (FontManager.Size.SMALL.val == size.val) {
            dip = 1;
        }
        borderTitle = new Font(ARIAL, Font.ITALIC, size.getValue() - dip);
        updateAll();
        if (fileProps != null) {
            fileProps.put(FONT_SIZE_KEY, "" + size.getValue());
        }
        sz = size;
    }

    /**
     * @param sizeString
     *            New size.
     */
    public void setSize(final String sizeString) {
        setSize(Size.find(Integer.parseInt(sizeString)));
    }

    /**
     * @return Size.
     */
    public Size getSize() {
        if (sz == null) {
            String s = "";
            if (fileProps != null) {
                s = fileProps.get(FONT_SIZE_KEY);
            }
            int i = FontManager.Size.SMALL.getValue();
            if (Strings.isPopulated(s)) {
                try {
                    i = Integer.parseInt(s);
                } catch (NumberFormatException ex) {
                    setSize(Size.SMALL);
                    return sz;
                }
                Long.parseLong(s);
            }
            setSize(Size.find(i));
        }
        return sz;
    }

    public int getRatio() {
        switch (getSize()) {
        case SMALL:
            return 1;
        case MEDIUM:
            return 1;
        case LARGE:
            return 2;
        case SUPER:
            return 2;
        case CINEMA:
            return 2;
        }
        return 1;
    }
    /**
     * Default size.
     */
    private Size sz;

    /**
     * @return Monospace font.
     */
    public Font getMonospace() {
        if (monospace == null) {
            getSize();
        }
        return monospace;
    }

    /**
     * @return Bold Arial font.
     */
    public Font getBoldArial() {
        if (boldArial == null) {
            getSize();
        }
        return boldArial;
    }

    /**
     * @return Plain Arial font.
     */
    public Font getPlainArial() {
        if (plainArial == null) {
            getSize();
        }
        return plainArial;
    }

    /**
     * @return Plain Arial font.
     */
    public Font getBorderTitle() {
        if (borderTitle == null) {
            getSize();
        }
        return borderTitle;
    }

    /**
     * Font classifications.
     */
    public enum FontType {

        /**
         * Plain Arial.
         */
        plainArial,
        /**
         * Bold Arial.
         */
        boldArial,
        /**
         * Monospace.
         */
        monospace,
        /**
         * Border title.
         */
        borderTitle
    }

    /**
     * SMALL, MEDIUM, LARGE.
     */
    public static enum Size {

        /**
         * Small size is 9.
         */
        SMALL(9),
        /**
         * Medium size is 12.
         */
        MEDIUM(12),
        /**
         * Large size is 16.
         */
        LARGE(16),
        /**
         * Extra large size is 24.
         */
        SUPER(24),
        /**
         * Cinema size is 48.
         */
        CINEMA(48);
        /**
         * Value.
         */
        private final int val;

        /**
         * @param fontSize
         *            Font size.
         */
        Size(final int fontSize) {
            val = fontSize;
        }

        /**
         * Find size.
         *
         * @param fontSize
         *            Required font size.
         * @return Next smallest size if no match found, medium at last resort.
         */
        public static Size find(final int fontSize) {
            for (Size size : Size.values()) {
                if (size.getValue() == fontSize) {
                    return size;
                }
            }
            for (Size size : Size.values()) {
                if (size.getValue() > fontSize) {
                    return size;
                }
            }
            return Size.MEDIUM;
        }

        /**
         * @return Font size.
         */
        public int getValue() {
            return val;
        }
    }

    /**
     * @param component
     *            to register.
     * @param fontType
     *            Font type.
     * @return The registered component.
     */
    private JComponent registerComponent(final JComponent component,
            final FontType fontType) {
        if (component == null || fontType == null) {
            throw new RuntimeException();
        }
        getCmps().put(component, fontType);
        component.setFont(getFontForType(fontType));
        component.setBackground(Color.WHITE);
        return component;
    }

    /**
     * @param component
     *            Component.
     * @param fontType
     *            Font type.
     * @return The registered component.
     */
    public JComponent register(final JComponent component,
            final FontType fontType) {
        return registerComponent(component, fontType);
    }

    /**
     * @param border
     *            Titled border.
     * @param fontType
     *            Font type.
     * @return The registered component.
     */
    public TitledBorder register(final TitledBorder border,
            final FontType fontType) {
        return registerTitleBorder(border, fontType);
    }

    /**
     * @param border
     *            to register.
     * @param fontType
     *            Font type.
     * @return The registered border.
     */
    private TitledBorder registerTitleBorder(final TitledBorder border,
            final FontType fontType) {
        if (border == null || fontType == null) {
            throw new RuntimeException();
        }
        getBrdrs().put(border, fontType);
        border.setTitleFont(getFontForType(fontType));
        return border;
    }

    /**
     * @param fontType
     *            Font type.
     * @return Font.
     */
    public Font getFont(final FontType fontType) {
        return getFontForType(fontType);
    }

    /**
     * @param fontType
     *            Font type.
     * @return Font.
     */
    private Font getFontForType(final FontType fontType) {
        try {
            Object rtn = FontManager.class.getMethod("get"

            + Strings.upperCaseFirstLetter(fontType.name()),
                    new Class<?>[0]).invoke(this, new Object[0]);
            if (rtn instanceof Font) {
                Font f = (Font) rtn;
                return f;
            }
            throw new NullPointerException();
        } catch (Exception ex) {
            Logger.log(ex);
            throw new RuntimeException(ex);
        }
    }

    /**
     * Update all registered fonts.
     */
    public synchronized void updateAll() {
        for (Entry<JComponent, FontType> e : getCmps().entrySet()) {
            e.getKey().setFont(getFontForType(e.getValue()));
            e.getKey().invalidate();
        }
        for (Entry<TitledBorder, FontType> e : getBrdrs().entrySet()) {
            e.getKey().setTitleFont(getFontForType(e.getValue()));
        }
    }

    /**
     * @return Registered components.
     */
    private Map<JComponent, FontType> getCmps() {
        if (cmps == null) {
            cmps = new HashMap<JComponent, FontType>();
        }
        return cmps;
    }

    /**
     * @return Registered borders.
     */
    private Map<TitledBorder, FontType> getBrdrs() {
        if (brdrs == null) {
            brdrs = new HashMap<TitledBorder, FontType>();
        }
        return brdrs;
    }

    /**
     * Components.
     */
    private static Map<JComponent, FontType> cmps;
    /**
     * Borders.
     */
    private static Map<TitledBorder, FontType> brdrs;

    /**
     * Based on SingletonHolder inner class by Bill Pugh.
     *
     *
     * <h5>References</h5>
     * <ul>
     * <li>
     * <a href="http://en.wikipedia.org/wiki/Singleton_pattern">
     * http://en.wikipedia.org/wiki/Singleton_pattern </a></li>
     * </ul>
     *
     */
    private static final class Singleton {
        /**
         * Instance variable.
         */
        private static final FontManager INSTANCE = new FontManager();

        /**
         * Private constructor.
         */
        private Singleton() {
            // Prevent instantiation.
        }
    }

    public void registerMenuComponent(final JComponent menu) {
        register(menu, FontManager.FontType.boldArial);
    }
    /**
     * @return static MathUtil instance.
     */
    public static FontManager getInstance() {
        return Singleton.INSTANCE;
    }

    /**
     * Private singleton constructor.
     */
    private FontManager() {
        // Prevent outside instantiation.
    }

    private FileProps fileProps;

    public void setProps(final FileProps appProps) {
        fileProps = appProps;
    }
}
